/*********************************************************************************
 *      Copyright:  (C) 2022 lastbreath<2631336290@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(12/01/22)
 *         Author:  lijiale <244871365@qq.com>
 *      ChangeLog:  1, Release initial version on "12/01/22 16:09:21"
 *                 
 ********************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/time.h>

#include "comport.h"

#define BAUDRATE 128

//设置串口
int comport_conf(comport_t *comport)
{
    int                 rc;
    char                baudrate[BAUDRATE]={0};
    struct termios      newtermios;

    if( !comport )
    {
        printf("invalid parameter.\n");
        return -1;
    }

    memset(&newtermios,0,sizeof(struct termios));
    memset(&(comport->oldtermios),0,sizeof(struct termios));
    
    printf("conf_comport->fd:%d\n",comport->fd);


    if( tcgetattr(comport->fd, &(comport->oldtermios)) )
    {
        printf("%s,get termios to oldtermios failure:%s\n",__func__,strerror(errno));
        return -2;
    }

    if(tcgetattr(comport->fd,&newtermios))
    {
        printf("%s,Get termios to newtermios failure:%s\n",__func__,strerror(errno));
        return -3;
    }

    /* 修改控制模式，保证不会占用串口 */
    newtermios.c_cflag |= CLOCAL;

    /* 启动接收器，能够从串口中读取输入数据 */
    newtermios.c_cflag |= CREAD;
    
    /*  CSIZE字符大小掩码，将与设置databits相关的标致位置零 */
    newtermios.c_cflag &= ~CSIZE;

    newtermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /*  
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志，ERASE字符删除前一个所输入的字符，WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符，生成一个相应的信号
     */

    newtermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /*  
     *  BRKINT:BREAK将会丢弃输入和输出队列中的数据(flush)，并且如果终端为前台进程组的控制终端，则BREAK将会产生一个SIGINT信号发送到这个前台进程组
     *  ICRNL: 将输入中的CR转换为NL
     *  INPCK: 允许奇偶校验
     *  ISTRIP: 剥离第8个bits
     *  IXON: 允许输出端的XON/XOF流控
     */
    
    /*  OPOST: 表示处理后输出，按照原始数据输出 */ 
    newtermios.c_oflag &= ~(OPOST);

    if( comport->baudrate )
    {
        sprintf(baudrate,"B%d",comport->baudrate);

        /* 设置输入输出波特率 */
        cfsetispeed(&newtermios,(speed_t)baudrate);
        cfsetospeed(&newtermios,(speed_t)baudrate);
    }
    else
    {
        cfsetispeed(&newtermios,B9600);
        cfsetospeed(&newtermios,B9600);
    }

    /* 设置数据位 */
    switch( comport->databits )
    {
        case '6':
            newtermios.c_cflag |= CS6;
            break;

        case '7':
            newtermios.c_cflag |= CS7;
            break;

        case '8':
            newtermios.c_cflag |= CS8;
            break;

        default:
            newtermios.c_cflag |= CS8;
            break;
    }

    /*  设置奇偶校验方式  */
    switch(comport->parity)
    {
        /*  无校验  */
        case 'n':
        case 'N':
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_iflag &= ~INPCK;
            break;

        /*  偶校验 */
        case 'e':
        case 'E':
            newtermios.c_cflag |= PARENB;
            newtermios.c_cflag &= ~PARODD;
            newtermios.c_iflag |= INPCK;
            break;

        /*  奇校验 */
        case 'o':
        case 'O':
            newtermios.c_cflag |= PARENB;
            newtermios.c_cflag |= PARODD;
            newtermios.c_iflag |= INPCK;
            break;

        /*  设置为空格  */
        case 's':
        case 'S':
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_cflag &= ~CSTOPB;
            break;

        /*  默认无校验 */
        default:
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_iflag &= ~INPCK;
            break;
    }

    /*  设置停止位  */
    switch( comport->stopbits )
    {
        case '1':
            newtermios.c_cflag &= ~CSTOPB;
            break;

        case '2':
            newtermios.c_cflag |= CSTOPB;
            break;

        default:
            newtermios.c_cflag &= ~CSTOPB;
            break;
    }

    /* 设置超时   */
    newtermios.c_cc[VTIME] = 0;  //最长等待时间
    newtermios.c_cc[VMIN] = 0;  //最小接收字符 

    comport->mSend_Len = 128;  //若命令长度大于mSend_Len,则每次最多发送为mSend_Len

    if( tcflush(comport->fd,TCIFLUSH) )
    {
        printf("%s,Failed to clear the cache:%s\n",__func__,strerror(errno));
        return -4;
    }
    
    if( tcsetattr(comport->fd,TCSANOW,&newtermios) )
    {
        printf("%s,tcsetcomport failure:%s\n",__func__,strerror(errno));
        return -5;
    }

    printf("Comport Conf  Successfully......\n");
    
    return 0;
}

int comport_open(comport_t *comport,char *devname)
{
    int              rc=-1;

    if( !comport || !devname)
    {
        printf("%s,Invalid parameter\n",__func__);
        return -1;
    }


    /*O_NOCTTY表示打开的是一个终端设备，程序不会成为该
     * 端口的控制终端,O_NONBLOCK使得read处于非阻塞模式 */
    comport->fd = open(devname,O_RDWR | O_NOCTTY | O_NONBLOCK);
    if( comport->fd < 0 )
    {
        printf("%s,Open %s failed:%s\n",__func__,comport->tty_name,strerror(errno));
        return -1;
    }

    /*  检查串口是否处于阻塞态  */
    if( (rc = fcntl(comport->fd,F_SETFL,0)) < 0 )
    {
        printf("%s,Fcntl check faile.\n",__func__);
        return -2;
    }

    if( 0 == isatty(comport->fd) )  //是否为终端设备
    {
        printf("%s:[%d] is not a Terminal equipment.\n",comport->tty_name,comport->fd);
        return -3;
    }

    printf("Open %s successfully.\n",comport->tty_name);

    return 0;
}

int comport_close(comport_t *comport)
{
    if(tcflush(comport->fd,TCIOFLUSH))  //清零用于串口通信的缓冲区
    {
        printf("%s,Tcflush faile:%s\n",__func__,strerror(errno));
        return -1;
    }

    /*  将串口设置为原有属性 */
    if(tcsetattr(comport->fd,TCSANOW,&(comport->oldtermios)))
    {
        printf("%s,Set old options failed:%s\n",__func__,strerror(errno));
        return -2;
    }

    close(comport->fd);

    return 0;
}


int comport_send(comport_t *comport,char *sbuf,int sbuf_len)
{
    char            *ptr = NULL;
    char            *end = NULL;
    int             rv;

    if( !comport || !sbuf || sbuf_len <= 0 )
    {
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }

    if( comport->fd < 0 )
    {
        printf("Serail not connected.\n");
        return -2;
    }

    if( comport->mSend_Len < sbuf_len )
    {
        ptr = sbuf;
        end = sbuf + sbuf_len;
        
        do
        {
            if( comport->mSend_Len < (end - ptr) )
            {
                rv = write(comport->fd,ptr,comport->mSend_Len);
                if( rv <= 0 || rv != comport->mSend_Len )
                {
                    printf("Write to com port[%d] failed:%s\n",comport->fd,strerror(errno));
                    return -3;
                }
                
                ptr += comport->mSend_Len;
            }
            else
            {
                rv = write(comport->fd,ptr,(end - ptr));
                if( rv <= 0 || rv != (end - ptr) )
                {
                    printf("Write to com port[%d] failed:%s\n",comport->fd,strerror(errno));
                    return -4;
                }


                ptr += (end - ptr);
            }
        }while(end > ptr);
    }
    else
    {
        printf("write:%s\b",sbuf);
        rv = write(comport->fd,sbuf,sbuf_len);
        if( rv <= 0 || rv != sbuf_len )
        {
            printf("Write to com port[[%d] failed:%s\n",comport->fd,strerror(errno));
            return -5;
        }
    }

    printf("comport_secd Successfully\n");

    return rv;
}

int comport_recv(comport_t *comport,char *rbuf, int rbuf_len, int  timeout)
{
    int             rv = 0;
    fd_set          rset;
    struct timeval  time_out;

    if( !rbuf || rbuf_len <= 0 )
    {
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }

    time_out.tv_sec = (time_t)(timeout / 1000);
    time_out.tv_usec = 0;

    FD_ZERO(&rset);
    FD_SET(comport->fd,&rset);
    
    while(1)
    {
        rv = select(comport->fd + 1,&rset,NULL,NULL,&time_out);
        if( rv < 0 )
        {
            printf("%s,Select failed:%s\n",strerror(errno));
            return -2;
        }
        else if( rv == 0 )
        {
            printf("Time Out.\n");
            return 0;
        }
        else
        {
            printf("select 监测到端口fd有消息\n");
            rv = read(comport->fd,rbuf,rbuf_len);
        }
        
        if( rv <= 0 )
        {
            printf("%s,Read failed:%s\n",__func__,strerror(errno));
            return -3;
        }
    }

    return rv;
}
